/**
 * 
 */
package br.edu.unifei.cct720.trabalho06.model.formas;

import java.awt.Color;

import br.edu.unifei.cct720.trabalho06.model.ponto.Ponto;
import br.edu.unifei.cct720.trabalho06.model.ponto.Pontos;

/**
 * Classe que representa a figura geometrica 
 * de uma reta
 * 
 * @author fknappe
 *
 */
public class Reta implements FormaGeometrica {

	// Constantes que identificam o tipo da reta
	public static final int HORIZONTAL = 1;
	public static final int VERTICAL = 2;
	public static final int DIAGONAL = 3;
	public static final int INCLINADA = 4;
	
	// Declaracao das variaveis de instancia
	private Ponto inicio;
	private Ponto fim;
	private double b;
	private double m;
	private Color cor;
	
	/**
	 * Construtor 
	 * 
	 * @param Ponto p1
	 * @param Ponto p2
	 * @param Color cor
	 */
	public Reta(Ponto anterior, Ponto ponto, Color cor) {
		// Deixa o ponto mais a esquerda como ponto inicial
		if (anterior.getX() < ponto.getX() ||
				(anterior.getX() == ponto.getX() && anterior.getY() < ponto.getY()) ) {
			this.inicio = anterior;
			this.fim = ponto;	
		} else {
			this.inicio = ponto;
			this.fim = anterior;			
		}
		
		// Calcula o valor de m
		if (inicio.getX() == fim.getX()) {
			m = Double.POSITIVE_INFINITY;
			b = inicio.getX();
		} else {
			m = ((double) (fim.getY() - inicio.getY())) / ((double) (fim.getX() - inicio.getX()));
			b = inicio.getY() - (m * inicio.getX());
		}
		
		this.cor = cor;
	}
	
	/**
	 * Metodo responsavel por construir 
	 * uma reta horizontal
	 * 
	 * @return Pontos
	 */
	private Pontos horizontal() {
		Pontos pontos = new Pontos();
		for (int x = inicio.getX(); x <= fim.getX(); x++)
			pontos.add(new Ponto(x, inicio.getY(), cor));	
		
		return pontos; 
	}
	
	/**
	 * Metodo responsavel por construir
	 * uma reta vertical
	 * 
	 * @return Pontos
	 */
	private Pontos vertical() {
		Pontos pontos = new Pontos();
		for (int y = inicio.getY(); y <= fim.getY(); y++)
			pontos.add(new Ponto(inicio.getX(), y, cor));		
		
		return pontos;	
	}
	
	/**
	 * Metodo responsavel por construir
	 * uma reta diagonal
	 * 
	 * @return Pontos
	 */
	private Pontos diagonal() {
		Pontos pontos = new Pontos();
		int r = 0;
		
		while((inicio.getX() + r) <= fim.getX()) {
			int tmp = ((int) m) * r;
			pontos.add(new Ponto(inicio.getX() + r, inicio.getY() + tmp, cor));
			r++;
		}	
		
		return pontos;
	}
	
	/**
	 * Metodo responsavel por construir
	 * uma reta inclinada
	 * 
	 * @return Pontos
	 */
	private Pontos inclinada() {
		Pontos pontos = new Pontos();
		int deltaX = fim.getX() - inicio.getX();
		int deltaY = fim.getY() - inicio.getY();
		int erro = 0;
		int x = inicio.getX();
		int y = inicio.getY();
		
		if ((Math.abs(deltaY) >= Math.abs(deltaX) && inicio.getY() > fim.getY()) 
				|| (Math.abs(deltaY) < Math.abs(deltaX) && (deltaY < 0))) {
			x = fim.getX();
			y = fim.getY();
			deltaX = inicio.getX() - fim.getX();
			deltaY = inicio.getY() - fim.getY();
		}
		
		pontos.add(inicio);
		if (deltaX >= 0) {
			if (Math.abs(deltaX) >= Math.abs(deltaY)) {
				// Caso 1
				for (int i = 1; i < Math.abs(deltaX); i++) {
					if (erro < 0) {
						x++;
						pontos.add(new Ponto(x, y, cor));
						erro += deltaY;
					} else {
						x++;
						y++;
						pontos.add(new Ponto(x, y, cor));
						erro += deltaY - deltaX;
					}
				}
			} else {
				// Caso 2
				for (int i = 1; i < Math.abs(deltaY); i++) {
					if (erro < 0) {
						x++;
						y++;
						pontos.add(new Ponto(x, y, cor));
						erro += deltaY - deltaX;
					} else {
						y++;
						pontos.add(new Ponto(x, y, cor));
						erro -= deltaX;
					}
				}
			}
		} else {
			if (Math.abs(deltaX) >= Math.abs(deltaY)) {
				// Caso 3
				for (int i = 1; i < Math.abs(deltaX); i++) {
					if (erro < 0) {
						x--;
						pontos.add(new Ponto(x, y, cor));
						erro += deltaY;
					} else {
						x--;
						y++;
						pontos.add(new Ponto(x, y, cor));
						erro += deltaY + deltaX;
					}
				}
			} else {
				// Caso 4
				for (int i = 1; i < Math.abs(deltaY); i++) {
					if (erro < 0) {
						x--;
						y++;
						pontos.add(new Ponto(x, y, cor));
						erro += deltaX + deltaY;
					} else {
						y++;
						pontos.add(new Ponto(x, y, cor));
						erro += deltaX;
					}
				}
			}
		}	
		pontos.add(fim);	
		
		return pontos;
	}
	
	/**
	 * Metodo responsavel pela verificacao
	 * se um determinado ponto esta contido
	 * na reta
	 * 
	 * @param Ponto ponto
	 * 
	 * @return int
	 */
	public int temPonto(Ponto ponto) {
		if (inicio.getX() == ponto.getX() && inicio.getY() == ponto.getY()) {
			return 1;
		}
		if (fim.getX() == ponto.getX() && fim.getY() == ponto.getY()) {
			return 2;
		}
		return 0;
	}
	
	/**
	 * Metodo que retorna o conjunto de pontos
	 * presentes na reta
	 * 
	 * @return Pontos
	 */
	public Pontos getPontos() {
		Pontos pontos = null;
		if (Double.isInfinite(m)) {
			pontos = vertical();
		} else if (m == 0) {
			pontos = horizontal();
		} else if (m == 1 || m == -1) {
			pontos = diagonal();
		} else {
			pontos = inclinada();
		}
		return pontos;
	}
	
	/**
	 * Metodo responsavel por identificar 
	 * o tipo da reta
	 * 
	 * @return int
	 */
	public int getTipo() {
		if (Double.isInfinite(m)) {
			return VERTICAL;
		} else if (m == 0) {
			return HORIZONTAL;
		} else if (m == 1 || m == -1) {
			return DIAGONAL;
		} else {
			return INCLINADA;
		}
	}
	
	// Metodos getters e setters
	public Ponto getInicio() {
		return inicio;
	}

	public Ponto getFim() {
		return fim;
	}
	
	public double getM() {
		return m;
	}
	
	public double getB() {
		return b;
	}
	
	public Color getCor() {
		return cor;
	}
}
